package com.lxu.finance.utils;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.StrSubstitutor;

import java.math.BigDecimal;
import java.util.*;

import static java.math.BigDecimal.ZERO;
import static java.math.RoundingMode.HALF_UP;

/**
 * 数学公式计算
 * @author chenbin
 */
public class RpnUtil {
        /**
         * 匹配浮点数
         */
        public static final String FLOAT_NUMBER = "^(-?\\d+)(\\.\\d+)?$";


    public static void main(String[] args) {
        Map<String,String> values = new HashMap<>();
        values.put("aaa#111#-","3");
        values.put("bbb#111#-","5");
        values.put("ccc#555#-","6");
        values.put("ddd#777#-","1");
        values.put("fff#111#-","2");
        String str ="${aaa#111#-} + ( ${bbb#111#-} * ( ${ccc#555#-} - ${ddd#777#-} / ${fff#111#-} ) )";

        List<String> test6 = Arrays.asList(str.split(" "));
        List<String> test5 = Arrays.asList("( ( 44.00 - 4.00 - 4.00 ) / 48.00 )  * 100".split(" "));
        List<String> test = Arrays.asList("3 + ( 5 * ( 6 + 1 * 2 ) )".split(" "));

        System.out.println(getRpn(test5));
        System.out.println(calculate(test5));
        /*System.out.println(getRpn(test));
        System.out.println(getRpn(test6));
        System.out.println(calculate(test));

        List<String> test2 = Arrays.asList(replaceStringPlaceHolder(values, str).split(" "));
        System.out.println(getRpn(test2));
        System.out.println(calculate(test2));*/
    }

    public static String replaceStringPlaceHolder( Map<String,String> valuesMap,String sql) {
        StrSubstitutor sub = new StrSubstitutor(valuesMap);
        return sub.replace(sql);
    }



    /**
     * 计算指定公式的数值
     */
    public static String calculate(List<String> formulaList) {
        List<String> rpn = getRpn(formulaList);
        return calculateRpn(rpn);
    }

    /**
     * 中序转后序
     */
    public static List<String> getRpn(List<String> formulaList) {
        Stack<String> operators=new Stack<>();
        List<String> result = new ArrayList<>();
        for (String formula : formulaList) {
            if (formula.matches(FLOAT_NUMBER)) {
                result.add(formula);
            }
            else if (formula.equals("(")) {
                operators.push(formula);
            }
            else if (formula.equals(")")) {
                while (!operators.peek().equals("(")) {
                    result.add(operators.pop());
                }
                operators.pop();
            }
            else {
                while (operators.size() != 0 && getLevel(operators.peek()) >= getLevel(formula)) {
                    result.add(operators.pop());
                }
                operators.push(formula);
            }
        }
        while (operators.size() != 0) {
            result.add(operators.pop());
        }
        return result;
    }

    /**
     * 判断字符串是否为数字
     * @param str
     * @return
     */
    public static boolean isNumber(String str){
        if (str.matches(FLOAT_NUMBER)) {
            return true;
        }
        return false;
    }

    /**
     * 逆序计算结果
     */
    public static String calculateRpn(List<String> formulaList) {
        Stack<BigDecimal> s=new Stack<>();
        for (String formula : formulaList) {
            if (formula.matches(FLOAT_NUMBER)) {
                s.push(new BigDecimal(formula));
            }
            else {
                BigDecimal b = s.pop();
                BigDecimal a = s.pop();
                BigDecimal temp = ZERO;
                switch (formula) {
                    case "+" : temp = a.add(b);break;
                    case "-" : temp = a.subtract(b);break;
                    case "*" : temp = a.multiply(b);break;
                    case "/" : temp = a.divide(b, 4, HALF_UP);break;
                    case "max" : temp = a.max(b);break;
                }
                s.push(temp);
            }
        }
        return s.pop().toString();
    }

    /**
     * 运算符优先级
     */
    private static int getLevel(String operator) {
        if (StringUtils.isBlank(operator)) {
            return 0;
        }
        switch (operator) {
            case "(" : return 0;
            case "+" :
            case "-" : return 1;
            case "*" :
            case "/" :
            case "max" : return 2;
            default : return -1;
        }
    }
}

